{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 配置LANGCHAIN_API_KEY，以便于在LangSmith进行追踪"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "os.environ[\"LANGCHAIN_TRACING_V2\"] = \"true\"\n",
    "os.environ[\"LANGCHAIN_API_KEY\"] = getpass.getpass()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 使用语言模型：Cohere\n",
    "pip install -qU langchain-cohere"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [],
   "source": [
    "import getpass\n",
    "import os\n",
    "\n",
    "os.environ[\"COHERE_API_KEY\"] = getpass.getpass()\n",
    "\n",
    "from langchain_cohere import ChatCohere\n",
    "\n",
    "model = ChatCohere(model=\"command-r\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "让我们首先直接使用模型。ChatModels是LangChain“Runnables”的实例，这意味着它们公开了一个与它们交互的标准接口。要简单地调用模型，我们可以将消息列表传递给.invoke方法。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "AIMessage(content='您好，很乐意为您服务！我们有各种口味的咖啡供您选择。我们有拿铁、摩卡、黑咖啡、咖啡摩卡和许多其他口味。您喜欢哪一种？', additional_kwargs={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'd597da30-4c7b-4647-92c9-c643773ccfcd', 'token_count': {'input_tokens': 75, 'output_tokens': 45}}, response_metadata={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'd597da30-4c7b-4647-92c9-c643773ccfcd', 'token_count': {'input_tokens': 75, 'output_tokens': 45}}, id='run-458bdc68-7f98-462b-86cf-efa9191323a5-0')"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "from langchain_core.messages import HumanMessage\n",
    "model.invoke([\n",
    "    HumanMessage(content=\"你好，我想要一杯咖啡。\")\n",
    "])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Message History\n",
    "我们可以使用消息历史类来包装我们的模型并使其有状态。这将跟踪模型的输入和输出，并将它们存储在一些数据存储中。然后，未来的交互将加载这些消息并将它们作为输入的一部分传递到链中。让我们看看如何使用它\n",
    "首先，让我们确保安装langchain-community，因为我们将在其中使用集成来存储消息历史记录。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# ! pip install langchain_community"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_community.chat_message_histories import ChatMessageHistory\n",
    "from langchain_core.chat_history import BaseChatMessageHistory\n",
    "from langchain_core.runnables.history import RunnableWithMessageHistory\n",
    "\n",
    "store = {}\n",
    "\n",
    "def get_session_history(session_id: str) -> BaseChatMessageHistory:\n",
    "    if session_id not in store:\n",
    "        store[session_id] = ChatMessageHistory()\n",
    "    return store[session_id]\n",
    "\n",
    "with_message_history = RunnableWithMessageHistory(model, get_session_history)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们现在需要创建一个config，我们每次都将其传递给runnable。此配置包含的信息不是直接输入的一部分，但仍然有用。在这种情况下，我们希望包含一个session_id。这应该如下所示："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "config = {\n",
    "    \"configurable\": {\n",
    "        \"session_id\": \"abc2\"\n",
    "    }\n",
    "}"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'很抱歉，我不能给你提供一杯真正的饮料。但是我可以告诉你如何制作一杯美味的生椰拿铁！\\n\\n你需要准备以下材料：\\n\\n- 鲜椰子肉（约200克）\\n- 冰块（约100克）\\n- 牛奶（约200毫升）\\n- 糖（可根据个人喜好调整量）\\n- 可可粉（可选）\\n\\n制作步骤：\\n\\n1. 将鲜椰子肉切成小块，并加入适量的冰块、牛奶和糖，放入搅拌机中。\\n\\n2. 高速搅拌直至椰子肉被充分碎化，变成细腻的椰子汁。\\n\\n3. 将搅拌好的椰子汁倒入高杯中，如果喜欢可可味，可再在杯子里加入一些可可粉。\\n\\n4. 最后在杯子上加入一小块椰子肉作为装饰，即可享受美味的生椰拿铁！\\n\\n如果你喜欢，还可以再添加一些酒，例如兰姆酒，就变成了一杯有酒的椰子鸡尾酒！'"
      ]
     },
     "execution_count": 7,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"你好，我想要一杯生椰拿铁。\")],\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你好！你刚才点了一杯生椰拿铁（Coco Nut Latte）。\\n\\n生椰拿铁是一杯非常清爽可口的咖啡饮品，以新鲜的椰子肉为主要材料，口感丰富且有层次。它将椰子的香甜与咖啡的苦香完美结合，是夏季非常受欢迎的咖啡品种之一。'"
      ]
     },
     "execution_count": 8,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"你好，我刚才点了一杯什么咖啡？\")],\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "太好了！我们的聊天机器人现在记住了关于我们的事情。如果我们更改配置以引用不同的session_id，我们可以看到它重新开始了对话。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你好！很抱歉，我没有办法准确地知道你刚才点了什么咖啡。因为我是一个语言模型，不能够实时地获取外部信息。\\n\\n但是你可以告诉我你记得点咖啡时的细节，比如你点的是哪种口味的咖啡、咖啡的外观或你当时在什么地方点了咖啡等。这样我就能更好地帮助你找到你刚才点的是什么咖啡了！'"
      ]
     },
     "execution_count": 24,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config = {\"configurable\": {\"session_id\": \"abc3\"}}\n",
    "\n",
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"你好，我刚才点了一杯什么咖啡？\")],\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "但是，我们总是可以回到最初的对话（因为我们将其持久化在数据库中）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'您好！您刚才点了一杯生椰拿铁。\\n\\n拿铁咖啡通常由两部分组成：底层的エスプレッソ（浓缩咖啡）和上层的蒸汽牛奶。而生椰拿铁则是一种变种的拿铁咖啡，它将普通蒸汽牛奶替换为带有椰子味道的牛奶。'"
      ]
     },
     "execution_count": 26,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "config = {\"configurable\": {\"session_id\": \"abc2\"}}\n",
    "\n",
    "response = with_message_history.invoke(\n",
    "    [HumanMessage(content=\"你好，我刚才点了一杯什么咖啡？\")],\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Prompt templates\n",
    "提示模板有助于将原始用户信息转换为LLM可以使用的格式。在这种情况下，原始用户输入只是一条消息，我们将其传递给LLM。现在让我们把它变得更复杂一点。首先，让我们添加一个带有一些自定义指令的系统消息（但仍将消息作为输入）。接下来，除了消息之外，我们将添加更多输入"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder\n",
    "\n",
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"你是一个乐于助人的助手。尽你所能回答所有问题。\"),\n",
    "    MessagesPlaceholder(variable_name=\"messages\")\n",
    "])\n",
    "\n",
    "chain = prompt | model"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "请注意，这稍微改变了输入类型-而不是传入消息列表，我们现在传入带有messages键的字典，其中包含消息列表。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 29,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你好！我很乐意帮助你。我可以为你准备一杯美味的咖啡。请问你喜欢哪一种咖啡？有多种口味供你选择：\\n\\n- 经典的黑咖啡，纯正濃郁的口感。\\n- 拿铁咖啡，添加了蒸汽牛奶，口感丰富柔顺。\\n- 摩卡咖啡，加入可可粉，味道甜美。\\n- 咖啡摩卡奇诺，一种更富含泡沫的咖啡饮品。\\n- 冰咖啡，一种凉爽提神的选择。\\n\\n你可以根据自己的喜好选择加糖和牛奶的量。如果你喜欢，我还可以为你添加一些装饰，比如巧克力粉或肉桂粉。'"
      ]
     },
     "execution_count": 29,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = chain.invoke(\n",
    "    {\n",
    "        \"messages\": [\n",
    "            HumanMessage(content=\"你好，我想要一杯咖啡。\"),\n",
    "        ]\n",
    "    }\n",
    ")\n",
    "response.content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们现在可以将其包装在与以前相同的消息历史对象中"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'您好！《三国演义》是中外闻名的中国古典小说，它以叙事精炼、人物丰满、描述细致而广受读者青睐。这部小说记录了中国三国时期的历史故事，作者是元末明初的作家——罗贯中。\\n\\n关于罗贯中的生平仍有许多未解之谜。他生活于元末明初，可能以罗本中或罗贯休为真名。还有传说称，他可能曾以“居士”的头衔生活过一段时间，因此《三国演义》中自称为“居士”。\\n\\n《三国演义》这本小说已成为中国文学的经典之作，它那波澜壮阔的叙事和精彩绝伦的篇章，吸引了无数读者。作为一部历史小说，它将真实的历史事件与虚构的情节巧妙结合，为读者带来了一段段精彩绝伦的阅读体验。\\n\\n罗贯中笔下的人物和故事被后世广泛地传承和演绎，他无疑是中华文化史上一位值得纪念的作家。'"
      ]
     },
     "execution_count": 18,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "with_message_history = RunnableWithMessageHistory(chain, get_session_history)\n",
    "config = {\n",
    "    \"configurable\": {\n",
    "        \"session_id\": \"abc5\"\n",
    "    }\n",
    "}\n",
    "response = with_message_history.invoke(\n",
    "    {\n",
    "        \"messages\": [\n",
    "            HumanMessage(content=\"你好，请问《三国演义》的作者是谁？\"),\n",
    "        ]    \n",
    "    },\n",
    "    config=config,\n",
    ")\n",
    "response.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'您好！您刚才问的是《三国演义》的作者是谁？\\n\\n《三国演义》是一部著名的历史小说，作者是元末明初的作家罗贯中。它讲述了中国三国时期的历史故事，被誉为经典之作，影响深远。'"
      ]
     },
     "execution_count": 19,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = with_message_history.invoke(\n",
    "    {\n",
    "        \"messages\": [\n",
    "            HumanMessage(content=\"你好，我刚才问的问题是什么\"),\n",
    "        ]    \n",
    "    },\n",
    "    config=config,\n",
    ")\n",
    "response.content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在让我们把提示变得更复杂一点。让我们假设提示模板现在看起来像这样"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 53,
   "metadata": {},
   "outputs": [],
   "source": [
    "prompt = ChatPromptTemplate.from_messages([\n",
    "    (\"system\", \"你是一个知识渊博的助手。请回答我的问题, 并将回答翻译成{language}。\"),\n",
    "    MessagesPlaceholder(variable_name=\"messages\")\n",
    "])\n",
    "chain = prompt | model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'*Three Kingdoms*, a classical novel of China, mainly narrates the historical events during the late Han Dynasty and the Three Kingdoms period. The story revolves around the rise and fall of various heroes and kingdoms in a tumultuous time. The main content can be summarized as follows:\\n\\nThe novel begins with the fall of the noble and righteous Lord Liu Bei, who loses his territory to the wicked Cao Cao. Liu Bei flees with his two sworn brothers, Guan Yu and Zhang Fei, and they swear the famous \"Brotherhood of the Peach Garden.\" Together, they seek to restore the Han Dynasty and fulfill their ambitions.\\n\\nThe narrative then introduces the wise and cunning military strategist Zhuge Liang, who joins Liu Bei and becomes his chief adviser. Zhuge Liang\\'s strategic prowess and diplomatic skills are showcased as he aids Liu Bei in establishing a solid foundation. Liu Bei declares himself King of Shu and establishes his capital in Chengdu.\\n\\nMeanwhile, Cao Cao, the warlord who seized power in the north, is depicted as a formidable adversary. His military conquests and political maneuvers consolidate his authority. Cao Cao\\'s ambitious nature and his pursuit of dominance over China form a central conflict point.\\n\\nThe story also delves into the adventures of the fearsome warrior Guan Yu, who embarks on numerous solo campaigns. His iconic battles, such as the famous \"Battle of Red Cliffs,\" showcase his unwavering loyalty to Liu Bei and his unparalleled martial prowess. However, Guan Yu ultimately meets his demise at the hands of Cao Cao\\'s forces.\\n\\nThe Kingdom of Wu, led by the charismatic Sun Quan, also emerges as a significant power in the south. The cunning and unscrupulous adviser, Zhou Yu, guides Sun Quan but later dies in a tragic incident. The romantic relationship between Zhou Yu\\'s widow, Xiao Qiao, and the musician Lu Su, adds a layer of intrigue to the story.\\n\\nThe novel goes on to describe the intricate alliances and conflicts between the three kingdoms of Shu, led by Liu Bei, Wei under Cao Cao, and Wu founded by Sun Quan. The strategic maneuvers, epic battles, and diplomatic negotiations among these kingdoms form the core of the narrative.\\n\\nAs the story unfolds, the rise of the ruthless and ambitious warlord, Jia Xu, adds complexity to the plot. His manipulation and eventual betrayal of the kind and righteous Cao Pi, Cao Cao\\'s successor, lead to a shift in the balance of power.\\n\\nThe latter part of the novel focuses on the eventual decline of the Shu Kingdom and the consolidation of power by the Wei Kingdom. The capture and execution of Liu Zan, the eccentric ruler of Shu, marks a significant turning point. The story concludes with the reunification of China under the Jin Dynasty, signaling an end to the Three Kingdoms era.\\n\\n*Three Kingdoms* is rich in detail and characterized by intricate political maneuvers, intense military campaigns, and the complex relationships between the numerous characters. It has become a timeless classic, influencing many artistic and cultural aspects of China and providing a fascinating glimpse into a tumultuous era in Chinese history. \\n\\nIn essence, it narrates the complex events of that period, showcasing the struggles and aspirations of these larger-than-life characters in a vivid and captivating manner.'"
      ]
     },
     "execution_count": 36,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = chain.invoke(\n",
    "    {\n",
    "        \"messages\": [HumanMessage(content=\"请讲述一下三国演义的主要内容\")],\n",
    "        \"language\": \"英文\"\n",
    "    }\n",
    ")\n",
    "response.content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在让我们将这个更复杂的链包装在一个消息历史类中。这次，因为输入中有多个键，我们需要指定正确的键来保存聊天记录。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 62,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Of course! How about these five delicious tropical fruits:\\n\\n1. Guava\\n2. Dragon Fruit\\n3. Passion Fruit\\n4. Lychee\\n5. Ackee\\n\\nTropical fruits are a delightful treat, offering a range of flavors, colors, and aromas that evoke a sense of warmth and exoticism.'"
      ]
     },
     "execution_count": 62,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "with_message_history = RunnableWithMessageHistory(\n",
    "    chain,\n",
    "    get_session_history,\n",
    "    input_messages_key=\"messages\",\n",
    ")\n",
    "\n",
    "config = {\n",
    "    \"configurable\": {\n",
    "        \"session_id\": \"abc6\"\n",
    "    }\n",
    "}\n",
    "\n",
    "response = with_message_history.invoke(\n",
    "    {\n",
    "        \"messages\": [HumanMessage(content=\"请说出五个热带水果的名字\")],\n",
    "        \"language\": \"英文\"\n",
    "    },\n",
    "    config=config,\n",
    ")\n",
    "\n",
    "response.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 63,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'Your previous question requested a list of five tropical fruit names. I provided an assortment of delicious fruits, including guava, dragon fruit, passion fruit, lychee, and ackee, each evoking a tropical paradise.'"
      ]
     },
     "execution_count": 63,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = with_message_history.invoke(\n",
    "    {\n",
    "        \"messages\": [HumanMessage(content=\"我上一个问题是什么？\")],\n",
    "        \"language\": \"中文\"\n",
    "    },\n",
    "    config=config,\n",
    ")\n",
    "# 这里的回答还是英文，难道是因为同一个config中的session_id相同，system message的参数不会变？\n",
    "response.content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Managing Conversation History 管理对话历史"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "构建聊天机器人时要理解的一个重要概念是如何管理对话历史。如果不加管理，消息列表将无限增长，并可能溢出LLM的上下文窗口。因此，添加一个限制您传入的消息大小的步骤非常重要。\n",
    "重要的是，您将希望在提示模板之前但在从消息历史记录加载以前的消息之后执行此操作"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "我们可以通过在提示符前面添加一个简单的步骤来适当地修改messages键，然后将新链包装在消息历史类中。首先，让我们定义一个函数来修改传入的消息。让我们使它选择k最近的消息。然后我们可以通过在开始时添加它来创建一个新链。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 131,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.runnables import RunnablePassthrough\n",
    "\n",
    "def filter_messages(messages, k=6):  # 只保留最近的k条消息\n",
    "    return messages[-k:] if len(messages) > k else messages\n",
    "\n",
    "chain = (\n",
    "    RunnablePassthrough.assign(messages=lambda x: filter_messages(x[\"messages\"]))\n",
    "    | prompt\n",
    "    | model\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "现在让我们将其包装在消息历史记录中"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 132,
   "metadata": {},
   "outputs": [],
   "source": [
    "from langchain_core.messages import AIMessage\n",
    "\n",
    "# 构建超过10条的消息\n",
    "messages = []"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 133,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[HumanMessage(content='My name is Bob'), AIMessage(content='你好，我又见到了你！', additional_kwargs={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'b0cf39ed-c56d-4cda-b0cd-fa5006fcce81', 'token_count': {'input_tokens': 114, 'output_tokens': 9}}, response_metadata={'documents': None, 'citations': None, 'search_results': None, 'search_queries': None, 'is_search_required': None, 'generation_id': 'b0cf39ed-c56d-4cda-b0cd-fa5006fcce81', 'token_count': {'input_tokens': 114, 'output_tokens': 9}}, id='run-9f1f3cf4-2ff1-46a1-a584-88351c0cca86-0')]\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "'你好，我又见到了你！'"
      ]
     },
     "execution_count": 133,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "with_message_history = RunnableWithMessageHistory(\n",
    "    chain,\n",
    "    get_session_history,\n",
    "    input_messages_key=\"messages\",\n",
    ")\n",
    "\n",
    "config = {\n",
    "    \"configurable\": {\n",
    "        \"session_id\": \"9527\"\n",
    "    }\n",
    "}\n",
    "\n",
    "response = with_message_history.invoke(\n",
    "    {\n",
    "        \"messages\": messages + [HumanMessage(content=\"My name is Bob\")],\n",
    "        \"language\": \"中文\",\n",
    "    },\n",
    "    config=config,\n",
    ")\n",
    "messages.append(HumanMessage(content=\"My name is Bob\"))\n",
    "messages.append(response)\n",
    "print(messages)\n",
    "response.content\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 134,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'哦，你兄弟叫杰克。'"
      ]
     },
     "execution_count": 134,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = with_message_history.invoke(\n",
    "    {\n",
    "        \"messages\": messages + [HumanMessage(content=\"My brother's name is Jack.\")],\n",
    "        \"language\": \"中文\",\n",
    "    },\n",
    "    config=config,\n",
    ")\n",
    "messages.append(HumanMessage(content=\"My brother's name is Jack.\"))\n",
    "messages.append(response)\n",
    "response.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 135,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你有个姐姐，真酷！她的名字叫安娜。'"
      ]
     },
     "execution_count": 135,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = with_message_history.invoke(\n",
    "    {\n",
    "        \"messages\": messages + [HumanMessage(content=\"My sister's name is Anna?\")],\n",
    "        \"language\": \"中文\",\n",
    "    },\n",
    "    config=config,\n",
    ")\n",
    "messages.append(HumanMessage(content=\"My sister's name is Anna?\"))\n",
    "messages.append(response)\n",
    "response.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 136,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'抱歉，我不能根据对话中的线索猜测你的名字。你可以告诉我吗？'"
      ]
     },
     "execution_count": 136,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = with_message_history.invoke(\n",
    "    {\n",
    "        \"messages\": messages + [HumanMessage(content=\"What's my name?\")],\n",
    "        \"language\": \"中文\",\n",
    "    },\n",
    "    config=config,\n",
    ")\n",
    "messages.append(HumanMessage(content=\"What's my name?\"))\n",
    "messages.append(response)\n",
    "response.content"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 137,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/plain": [
       "'你的姐姐名叫安娜。'"
      ]
     },
     "execution_count": 137,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "response = with_message_history.invoke(\n",
    "    {\n",
    "        \"messages\": messages + [HumanMessage(content=\"What's my sister name?\")],\n",
    "        \"language\": \"中文\",\n",
    "    },\n",
    "    config=config,\n",
    ")\n",
    "messages.append(HumanMessage(content=\"What's my sister name?\"))\n",
    "messages.append(response)\n",
    "response.content"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Streaming 流\n",
    "现在我们有了一个功能聊天机器人。然而，聊天机器人应用程序的一个真正重要的用户体验考虑是流式传输。LLM有时需要一段时间才能响应，因此为了改善用户体验，大多数应用程序都会在生成每个令牌时流式传输。这允许用户看到进度。\n",
    "\n",
    "所有chain都公开了一个.stream方法，使用消息历史记录的链也不例外。我们可以简单地使用该方法来获取流响应"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 139,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "《三国演义》是中国一部经典的章回小说，讲述了中国三国时期复杂的政治事件和战争。故事围绕着刘备、关羽和张飞三位英雄人物展开，他们一起结义，共同建立一个统一的王国。\n",
      "\n",
      "故事开始于汉末乱世，刘备在沔阳被吕布攻城时结识了义士关羽、张飞，三人一起在桃园结为兄弟，誓言共谋大业。随后他们招募人才，逐步壮大，其中最著名的是周瑜、诸葛亮等战略天才的加入。刘备带领兄弟们历经多场大战，最终建立蜀汉政权。\n",
      "\n",
      "另一方面，曹操麾下也有许多能臣猛将，如夏侯惇、张辽等，他统一北方建立魏国。东吴孙权也率军抵抗刘备和曹操的部队。三国之间不断发生冲突和战斗，如著名赤壁大战，周瑜率军大败曹操。随后，孙刘联盟一起抗魏。\n",
      "\n",
      "小说中还描述了许多著名历史事件，如刘备夺回徐州、关羽水淹七军、诸葛亮智取汉中、周瑜计取益阳、吕布助刘备得了徐州等。整个故事情节曲折离奇，人物形象丰富多彩，展现了复杂的社会政治、军事斗争、忠义友情、家庭恩情等内容。\n",
      "\n",
      "最后，蜀汉刘备兵败，魏国司马炎统一天下。这部小说被广泛地改编成各种戏剧、电视和电影，在中国家喻户晓，也是研究三国时期历史的重要参考资料。"
     ]
    }
   ],
   "source": [
    "for r in with_message_history.stream(\n",
    "    {\n",
    "        \"messages\": [HumanMessage(content=\"请用400字概括一下《三国演义》的故事情节。\")],\n",
    "        \"language\": \"中文\",\n",
    "    },\n",
    "    config=config,\n",
    "):\n",
    "    print(r.content, end=\"\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "langchain-demo",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
